﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;


using static BorderlessForm.NativeCodes.NativeMethods;
using static BorderlessForm.NativeCodes.NativeConstants;
using static BorderlessForm.NativeCodes.NativeStructures;
//2023.4.18----新增SetParent，修复任务栏无图标的bug
namespace BorderlessForm
{
    public partial class BorderlessForm : Form
    {
        public Color OnFocusBorderColor;
        public Color LossFocusBorderColor;
        IntPtr aHwd = IntPtr.Zero;

        #region 字段
        // 是否渲染阴影
        private bool _showShadow = true;

        private ShadowForm _shadow;
        // 正在构建阴影
        private bool _buildingShadow;

        // 标识用户通过鼠标操作调整尺寸或移动窗口
        private bool _userSizedOrMoved;
        #endregion

        #region 设计器

        /// <summary>
        /// 获取或设置一个值，该值指示是否渲染窗体的阴影。
        /// </summary>
        [Browsable(true)]
        [Category("Appearance")]
        [Description("获取或设置一个值，该值指示是否渲染窗体的阴影。")]
        [DefaultValue(true)]
        public bool Shadow
        {
            get => _showShadow;
            set
            {
                _showShadow = value;
                if (DesignMode)
                    return;

                if (_showShadow && Visible)
                    BuildShadow();
                else
                    _shadow?.Hide();
            }
        }
        #endregion

        #region 方法
        /// <summary>
        /// 绘制边框
        /// </summary>
        private void DrawBorder()
        {
            IntPtr hdc = GetWindowDC(Handle);
            Graphics g = Graphics.FromHdc(hdc);

            g.DrawRectangle(new Pen(Color.Black, 1), 0, 0, this.Width + -1, this.Height + -1);

            g.Dispose();
            ReleaseDC(Handle, hdc);
        }

        /// <summary>
        /// 创建阴影位图
        /// </summary>
        /// <returns></returns>
        private Bitmap CreateShadowBitmap()
        {
            Bitmap bmpBackground = new Bitmap(Width + 2 * 4, Height + 2 * 4);

            GraphicsPath gp = new GraphicsPath();
            gp.AddRectangle(new Rectangle(0, 0, bmpBackground.Width, bmpBackground.Height));

            using (Graphics g = Graphics.FromImage(bmpBackground))
            using (PathGradientBrush brs = new PathGradientBrush(gp))
            {
                g.CompositingMode = CompositingMode.SourceCopy;
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                g.SmoothingMode = SmoothingMode.AntiAlias;

                // 中心颜色
                brs.CenterColor = Color.FromArgb(100, Color.Black);
                // 指定从实际阴影边界到窗口边框边界的渐变
                brs.FocusScales = new PointF(1 - 2 * 4F / Width, 1 - 2 * 4F / Height);
                // 边框环绕颜色
                brs.SurroundColors = new[] { Color.FromArgb(0, 0, 0, 0) };
                // 掏空窗口实际区域
                gp.AddRectangle(new Rectangle(2 * 2, 2 * 2, Width, Height));
                g.FillPath(brs, gp);
            }

            gp.Dispose();
            return bmpBackground;
        }


        /// <summary>
        /// 创建阴影窗口
        /// </summary>
        private void BuildShadow()
        {
            if (DesignMode) return;

            lock (this)
            {
                _buildingShadow = true;

                if (_shadow != null && !_shadow.IsDisposed && !_shadow.Disposing)
                {
                    // 解除父子窗口关系
                    SetWindowLong(
                        Handle,
                        GWL_HWNDPARENT,
                        0);

                    _shadow.Dispose();
                }

                _shadow = new ShadowForm(CreateShadowBitmap());
                _shadow.pHwd = aHwd;
                _buildingShadow = false;

                AlignShadowPos(true);

                if (Visible)
                    _shadow.Show();

                // 设置父子窗口关系
                //SetWindowLong(
                //    Handle,
                //    GWL_HWNDPARENT,
                //    _shadow.Handle.ToInt32());
                
                SetParent(_shadow.Handle, Handle);
                Activate();
            }
        }
        /// <summary>
        /// 对齐阴影窗口位置
        /// </summary>
        /// <param name="disconnectedFromForeground">指定已解除与前台窗口的父子关系</param>
        private void AlignShadowPos(bool disconnectedFromForeground = false)
        {
            if (DesignMode) return;

            lock (this)
            {
                if (_shadow == null || _shadow.IsDisposed || _shadow.Disposing || _buildingShadow) return;

                GetWindowRect(Handle, out RECT rect);

                // 解除父子窗口关系
                if (!disconnectedFromForeground)
                    SetWindowLong(Handle, GWL_HWNDPARENT, 0);

                SetWindowPos(_shadow.Handle,
                    IntPtr.Zero,
                    rect.Left - 2 * 2,
                    rect.Top - 2 * 2,
                    rect.Width + 2 * 4,
                    rect.Height + 2 * 4,
                    SWP_NOZORDER | SWP_NOACTIVATE);

                // 设置父子窗口关系
                if (!disconnectedFromForeground)
                {
                    SetWindowLong(Handle, GWL_HWNDPARENT, _shadow.Handle.ToInt32());
                    Activate();
                }
            }
        }

        /// <summary>
        /// 对齐阴影窗口位置和尺寸
        /// </summary>
        private void AlignShadowPosSize()
        {
            if (DesignMode) return;

            lock (this)
            {
                _buildingShadow = true;

                if (_shadow == null || _shadow.IsDisposed || _shadow.Disposing)
                    return;

                _shadow.Hide();

                _shadow.UpdateBmp(CreateShadowBitmap());

                _buildingShadow = false;

                AlignShadowPos(true);

                if (Visible)
                    _shadow.Show();

                Activate();
            }//end of lock(this)
        }

        #endregion

        #region constants
        #region
        public const int CS_DROPSHADOW = 0x20000;
        public const uint WS_THICKFRAME = 0x00040000;
        public const int WVR_VALIDRECTS = 0x0400;
        public const int WM_PAINT = 0x000F;
        #endregion
        public const int GWL_HWNDPARENT = -8;

        const int WM_NCACTIVATE = 0x86;
        const int WM_NCPAINT = 0x85;
        const int WM_NCLBUTTONDOWN = 0xA1;
        const int WM_NCRBUTTONDOWN = 0x00A4;
        const int WM_NCRBUTTONUP = 0x00A5;
        const int WM_NCMOUSEMOVE = 0x00A0;
        const int WM_NCLBUTTONUP = 0x00A2;
        const int WM_NCCALCSIZE = 0x0083;
        const int WM_NCMOUSEHOVER = 0x02A0;
        const int WM_NCMOUSELEAVE = 0x02A2;
        const int WM_NCHITTEST = 0x0084;
        const int WM_NCCREATE = 0x0081;
        //const int WM_RBUTTONUP = 0x0205;

        const int WM_LBUTTONDOWN = 0x0201;
        const int WM_CAPTURECHANGED = 0x0215;
        const int WM_LBUTTONUP = 0x0202;
        const int WM_SETCURSOR = 0x0020;
        const int WM_CLOSE = 0x0010;
        public const int WM_SYSCOMMAND = 0x0112;
        const int WM_MOUSEMOVE = 0x0200;
        const int WM_SIZE = 0x0005;
        const int WM_SIZING = 0x0214;
        const int WM_GETMINMAXINFO = 0x0024;
        const int WM_ENTERSIZEMOVE = 0x0231;
        const int WM_WINDOWPOSCHANGING = 0x0046;


        // FOR WM_SIZING MSG WPARAM
        const int WMSZ_BOTTOM = 6;
        const int WMSZ_BOTTOMLEFT = 7;
        const int WMSZ_BOTTOMRIGHT = 8;
        const int WMSZ_LEFT = 1;
        const int WMSZ_RIGHT = 2;
        const int WMSZ_TOP = 3;
        const int WMSZ_TOPLEFT = 4;
        const int WMSZ_TOPRIGHT = 5;

        // left mouse button is down.
        const int MK_LBUTTON = 0x0001;

        const int SC_CLOSE = 0xF060;
        const int SC_MAXIMIZE = 0xF030;
        const int SC_MINIMIZE = 0xF020;
        const int SC_RESTORE = 0xF120;
        const int SC_CONTEXTHELP = 0xF180;

        public const int HTCAPTION = 2;
        const int HTCLOSE = 20;
        const int HTHELP = 21;
        const int HTMAXBUTTON = 9;
        const int HTMINBUTTON = 8;

        const int SM_CYBORDER = 6;
        const int SM_CXBORDER = 5;
        const int SM_CYCAPTION = 4;

        const int CS_DropSHADOW = 0x20000;
        const int GCL_STYLE = (-26);

        #endregion

        #region windows api
        #region 移动窗口windows api
        [DllImport("user32.dll")]
        public static extern bool ReleaseCapture();
        [DllImport("user32.dll")]
        public static extern bool SendMessage(IntPtr hwnd, int wMsg, int WParam, int lParam);
        public const int SC_MOVE = 0xF010;
        #endregion

        [DllImport("USER32.dll", SetLastError = true)]
        public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
        [DllImport("user32.dll")]
        public static extern int GetSystemMetrics(int nIndex);
        [DllImport("user32.dll")]
        public static extern int GetForegroundWindow();


        [DllImport("user32.dll")]
        public static extern int ReleaseDc(IntPtr hwnd, IntPtr hdc);


        [DllImport("User32.dll")]
        private static extern IntPtr GetWindowDC(IntPtr hwnd);
        //[DllImport("User32.dll")]
        //[return: MarshalAs(UnmanagedType.Bool)]
        //private static extern bool GetWindowRect(IntPtr hwnd, ref RECT rect);
        [DllImport("User32.dll")]
        private static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int SetClassLong(IntPtr hwnd, int nIndex, int dwNewLong);
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int GetClassLong(IntPtr hwnd, int nIndex);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern uint GetWindowLong(IntPtr hwnd, int nIndex);

        [DllImport("USER32.dll")]
        public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
        #endregion


        public BorderlessForm()
        {
            base.FormBorderStyle = FormBorderStyle.None;
            SetStyle(ControlStyles.DoubleBuffer
                     | ControlStyles.AllPaintingInWmPaint
                     | ControlStyles.UserPaint
                , true);
            OnFocusBorderColor = Color.Black;
            LossFocusBorderColor = Color.White;
            //Padding = new Padding(2);
        }



        #region 重写

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;

                cp.Style |= CS_DROPSHADOW | (int)WS_THICKFRAME;
                return cp;
            }
        }




        /// <inheritdoc />
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_NCACTIVATE:
                    {
                        IntPtr hDC = GetWindowDC(m.HWnd);
                        Graphics g = Graphics.FromHdc(hDC);
                        if (m.WParam == (IntPtr)1)
                        {

                            g.DrawRectangle(new Pen(Color.Black, 1), 0, 0, this.Width + -1, this.Height + -1);
                        }
                        else
                        {
                            g.DrawRectangle(new Pen(Color.White, 1), 0, 0, this.Width + -1, this.Height + -1);
                        }
                        //g.Dispose();
                        //ReleaseDc(m.HWnd, hDC);
                        m.LParam = IntPtr.Zero - 1;
                        base.WndProc(ref m);
                    }
                    break;
                case WM_NCPAINT:
                    {

                        int BorderWidth = 3 + GetSystemMetrics(92);
                        int cx = GetSystemMetrics(16);
                        int cy = GetSystemMetrics(17);
                        RECT rec = new RECT();
                        IntPtr hDC = GetWindowDC(m.HWnd);
                        GetWindowRect(this.Handle, out rec);
                        Graphics g = Graphics.FromHdc(hDC);
                        if (rec.Height >= Screen.PrimaryScreen.WorkingArea.Height && this.WindowState == FormWindowState.Maximized)
                        {
                            RECT rt = Screen.PrimaryScreen.WorkingArea;
                            cy = rt.Bottom;

                            if (Screen.PrimaryScreen.WorkingArea.Height == SystemParameters.PrimaryScreenHeight)
                            {                                
                                MoveWindow(this.Handle, -BorderWidth, -BorderWidth, cx + BorderWidth * 2, cy + BorderWidth * 2 - 8, true);
                                Padding = new Padding(0);
                                _shadow.Hide();
                            }
                            else
                            {
                                MoveWindow(this.Handle, -BorderWidth, -BorderWidth, cx + BorderWidth * 2, cy + BorderWidth * 2, true);
                                Padding = new Padding(0);
                            }
                        }
                        else
                        {
                            Padding = new Padding(2);
                        }
                        //隐藏底部任务栏情况下调整窗口高度
                        //显示器工作区高度=屏幕高度&&窗口高度<显示器工作区域
                        if (Screen.PrimaryScreen.WorkingArea.Height == SystemParameters.PrimaryScreenHeight && this.Height < Screen.PrimaryScreen.WorkingArea.Height && this.WindowState == FormWindowState.Maximized)
                        {
                            
                            MoveWindow(this.Handle, -BorderWidth, -BorderWidth, cx + BorderWidth * 2, Screen.PrimaryScreen.WorkingArea.Height, true);
                            Padding = new Padding(0);
                            _shadow.Hide();
                        }
                        DrawBorder();
                        m.Result = IntPtr.Zero;
                        
                        base.WndProc(ref m);
                    }
                    break;
                case WM_NCHITTEST:
                    {
                        base.WndProc(ref m);
                        _userSizedOrMoved = true;
                        Point pt = PointToClient(new Point((int)m.LParam & 0xFFFF, (int)m.LParam >> 16 & 0xFFFF));

                        if (pt.Y < 0)
                        {
                            m.Result = (IntPtr)HTCAPTION;
                            _userSizedOrMoved = false;
                        }

                        Point ptScreen = PointToScreen(pt);
                        const int borderRegionWidth = 2 + 2;
                        bool bTop = ptScreen.Y >= Top && ptScreen.Y <= Top + borderRegionWidth + 4;
                        bool bBottom = ptScreen.Y <= Bottom && ptScreen.Y >= Bottom - borderRegionWidth;
                        bool bLeft = ptScreen.X >= Left && ptScreen.X <= Left + borderRegionWidth + 2;
                        bool bRight = ptScreen.X <= Right + 18 && ptScreen.X >= Right - 18;

                        //if (bLeft)
                        //{
                        //    if (bTop)
                        //        m.Result = (IntPtr)HTTOPLEFT;
                        //    else if (bBottom)
                        //        m.Result = (IntPtr)HTBOTTOMLEFT;
                        //    else
                        //        m.Result = (IntPtr)HTLEFT;
                        //}
                        //else if (bRight)
                        //{
                        //    if (bTop)
                        //        m.Result = (IntPtr)HTTOPRIGHT;
                        //    else if (bBottom)
                        //        m.Result = (IntPtr)HTBOTTOMRIGHT;
                        //    else
                        //        m.Result = (IntPtr)HTRIGHT;
                        //}
                        //else if (bTop)
                        //{
                        //    m.Result = (IntPtr)HTTOP;
                        //}
                        //else if (bBottom)
                        //{
                        //    m.Result = (IntPtr)HTBOTTOM;
                        //}
                        break;
                    }
                case WM_NCCALCSIZE:
                    {
                        aHwd = m.HWnd;
                        // 自定义客户区
                        if (m.WParam != IntPtr.Zero)
                        {

                            NCCALCSIZE_PARAMS paramsIn = (NCCALCSIZE_PARAMS)
                                Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));

                            // 窗口移动调整后
                            RECT rectWndAfterMovedOrResized = paramsIn.rgrc[0];
                            // 窗口移动调整前
                            RECT rectWndBeforeMovedOrResized = paramsIn.rgrc[1];
                            // 客户区移动调整前
                            RECT rectClientBeforeMovedOrResized = paramsIn.rgrc[2];

                            NCCALCSIZE_PARAMS paramsOut = new NCCALCSIZE_PARAMS
                            {
                                rgrc = new RECT[3],
                                lppos = paramsIn.lppos
                            };

                            // 客户区调整后
                            RECT rectClientAfterMovedOrResized;
                            if ((rectWndAfterMovedOrResized.Right - rectWndAfterMovedOrResized.Left) >= GetSystemMetrics(16))
                            {
                                rectClientAfterMovedOrResized = new RECT(
                              rectWndAfterMovedOrResized.Left,
                              rectWndAfterMovedOrResized.Top,
                              rectWndAfterMovedOrResized.Right,
                              rectWndAfterMovedOrResized.Bottom);
                                if (Screen.PrimaryScreen.WorkingArea.Height == SystemParameters.PrimaryScreenHeight)
                                {

                                    int BorderWidth = 4 + GetSystemMetrics(92);
                                    rectClientAfterMovedOrResized = new RECT(
                               rectWndAfterMovedOrResized.Left,
                               rectWndAfterMovedOrResized.Top,
                               rectWndAfterMovedOrResized.Right,
                               rectWndAfterMovedOrResized.Bottom + BorderWidth);
                                }
                            }
                            else
                            {
                                int BorderWidth = 4 + GetSystemMetrics(92);
                                rectClientAfterMovedOrResized = new RECT(
                              rectWndAfterMovedOrResized.Left - BorderWidth,
                              rectWndAfterMovedOrResized.Top - BorderWidth,
                              rectWndAfterMovedOrResized.Right + BorderWidth,
                              rectWndAfterMovedOrResized.Bottom + BorderWidth);
                            }

                            //RECT rectClientAfterMovedOrResized = new RECT(
                            //   rectWndAfterMovedOrResized.Left + 2,
                            //   rectWndAfterMovedOrResized.Top + 2 + 30,
                            //   rectWndAfterMovedOrResized.Right - 2,
                            //   rectWndAfterMovedOrResized.Bottom + 30 - 2);


                            paramsOut.rgrc[0] = rectClientAfterMovedOrResized;
                            paramsOut.rgrc[1] = rectWndBeforeMovedOrResized;
                            paramsOut.rgrc[2] = rectClientBeforeMovedOrResized;

                            Marshal.StructureToPtr(paramsOut, m.LParam, false);

                            m.Result = (IntPtr)WVR_VALIDRECTS;
                        }
                        else
                        {
                            RECT @params = (RECT)
                                Marshal.PtrToStructure(m.LParam, typeof(RECT));

                            @params.Left += 2;
                            @params.Top += 2;
                            @params.Right -= 2;
                            @params.Bottom -= 2;

                            Marshal.StructureToPtr(@params, m.LParam, false);

                            m.Result = (IntPtr)1;
                        }

                        base.WndProc(ref m);
                        break;
                    }
                case WM_NCLBUTTONUP:
                    {
                        base.WndProc(ref m);

                        switch (m.WParam.ToInt32())
                        {
                            case HTCLOSE:
                                SendMessage(Handle, WM_SYSCOMMAND, SC_CLOSE, 0);
                                break;
                            case HTMAXBUTTON:
                                if (MaximizeBox)
                                {
                                    int wParam = WindowState == FormWindowState.Maximized
                                        ? SC_RESTORE
                                        : SC_MAXIMIZE;
                                    SendMessage(Handle, WM_SYSCOMMAND, wParam, 0);
                                }
                                break;
                            case HTMINBUTTON:
                                if (MinimizeBox)
                                    SendMessage(Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
                                break;
                        }
                        break;
                    }
                case WM_PAINT:
                    {

                        base.WndProc(ref m);
                        SendMessage(Handle, WM_NCPAINT, 0, 0);
                    }
                    break;
                case WM_WINDOWPOSCHANGED:
                    {
                        base.WndProc(ref m);
                        AlignShadowPos(true);
                    }
                    break;
                case WM_ENTERSIZEMOVE:
                    {
                        base.WndProc(ref m);

                        if (_userSizedOrMoved && _showShadow)
                            _shadow.Hide();
                    }
                    break;
                case WM_EXITSIZEMOVE:
                    {
                        base.WndProc(ref m);

                        if (_userSizedOrMoved && _showShadow)
                            AlignShadowPosSize();

                        break;
                    }
                default:
                    base.WndProc(ref m);
                    break;
            }
        }
        #endregion 重写

        private void Form4_MouseDown(object sender, MouseEventArgs e)
        {
            ReleaseCapture();
            SendMessage(this.Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
        }

        private void panel1_MouseDown(object sender, MouseEventArgs e)
        {
            ReleaseCapture();
            SendMessage(this.Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
        }


        /// <inheritdoc />
        protected override void OnShown(EventArgs e)
        {
            base.OnShown(e);

            if (_showShadow && _shadow == null)
                BuildShadow();
        }

        /// <inheritdoc />
        protected override void OnVisibleChanged(EventArgs e)
        {
            base.OnVisibleChanged(e);

            if (DesignMode)
                return;

            if (!_showShadow)
                return;

            bool visible = Visible;

            lock (this)
            {
                if (_shadow != null && _shadow?.Visible != visible)
                    _shadow.Visible = visible;
            }
        }


        /// <inheritdoc />
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            /*
             * 因调用SetWindowLong强制设置父子窗口关系，
             * 导致Application.Run创建模态消息循环的时候不会被视为模态窗口，
             * 故base.OnLoad中不会调用CenterToScreen，在此补全
             */
            if (StartPosition == FormStartPosition.CenterScreen)
                CenterToScreen();
        }


        /// <inheritdoc />
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);

            if (WindowState == FormWindowState.Minimized)
                return;

            DrawBorder();
            if (_shadow != null && _shadow.Visible)
                AlignShadowPosSize();
            Refresh();
        }

    }
}
